use std::ops::Sub;

#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
struct Date {
    year: u32,
    month: u8,
    day: u8,
}
impl Date {
    fn new(year: u32, month: u8, day: u8) -> Date {
        Date { year, month, day }
    }

    // 不实现 FromStr，因为 Option 的糖会更简洁一些
    fn from_str(s: &str) -> Option<Self> {
        let mut iter = s.splitn(3, '-');
        let year = iter.next()?.parse().ok()?;
        let month = iter.next()?.parse().ok()?;
        let day = iter.next()?.parse().ok()?;
        Some(Date::new(year, month, day))
    }

    fn is_leap_year(year: u32) -> bool {
        year % 4 == 0 && year % 100 != 0 || year % 400 == 0
    }
    fn year_days(year: u32) -> u32 {
        if Date::is_leap_year(year) {
            366
        } else {
            365
        }
    }

    fn days_of_month(year: u32, month: u8) -> u32 {
        match month {
            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
            4 | 6 | 9 | 11 => 30,
            2 => {
                if Date::is_leap_year(year) {
                    29
                } else {
                    28
                }
            }
            _ => 0,
        }
    }

    /// 获取该日是当年的第几天，从 1 开始
    pub fn day_in_year(&self) -> u32 {
        let mut rank = self.day as u32;
        for month in 1..self.month {
            rank += Date::days_of_month(self.year, month);
        }
        rank
    }

    /// 获取该日是星期几，0-6 分别代表周一到周日
    pub fn day_in_week(&self) -> u8 {
        // 2000-01-01 是星期六
        ((self - &Date::new(2000, 1, 1)) + 5).rem_euclid(7) as u8
    }

    /// 获取该日是当年的第几周，从 1 开始
    pub fn week_in_year(&self) -> u32 {
        let day = self.day_in_week();
        let nearest_thursday = self.add_days(4 - (day + 1) as i32);
        nearest_thursday.day_in_year().div_ceil(7)
    }

    /// 返回一个新的 Date，表示该日期后 days 天的日期
    pub fn add_days(&self, mut days: i32) -> Date {
        let mut new_date = Date::new(self.year, 1, 1);
        days += self.day_in_year() as i32;
        while days < 0 {
            new_date.year -= 1;
            days += Date::year_days(new_date.year) as i32;
        }
        loop {
            let rem = days - Date::year_days(new_date.year) as i32;
            if rem < 0 {
                break;
            }
            new_date.year += 1;
            days = rem;
        }
        loop {
            let rem = days - Date::days_of_month(new_date.year, new_date.month) as i32;
            if rem < 0 {
                break;
            }
            new_date.month += 1;
            days = rem;
        }
        new_date.day = days as u8;
        new_date
    }
}

/// 日期相减，返回两个日期之间的天数差
impl Sub for &Date {
    type Output = i32;

    fn sub(self, rhs: Self) -> Self::Output {
        if self < rhs {
            -rhs.sub(self)
        } else {
            let mut days = self.day_in_year() as i32 - rhs.day_in_year() as i32;
            for year in rhs.year..self.year {
                days += Date::year_days(year) as i32;
            }
            days
        }
    }
}

// 1970-2099 每一年春节的 UNIX-Date 日期戳
const LUNAR_SPRING_DELTA: [u32; 130] = [
    36, 391, 775, 1129, 1483, 1867, 2221, 2605, 2959, 3314, 3698, 4053, 4407, 4791, 5145, 5529,
    5883, 6237, 6621, 6976, 7331, 7715, 8069, 8423, 8806, 9161, 9545, 9899, 10254, 10638, 10992,
    11346, 11730, 12084, 12439, 12823, 13177, 13562, 13916, 14270, 14654, 15008, 15362, 15746,
    16101, 16485, 16839, 17194, 17578, 17932, 18286, 18670, 19024, 19379, 19763, 20117, 20501,
    20855, 21209, 21593, 21948, 22302, 22686, 23041, 23425, 23779, 24133, 24517, 24871, 25225,
    25609, 25964, 26319, 26703, 27057, 27441, 27795, 28149, 28533, 28887, 29242, 29626, 29981,
    30365, 30719, 31073, 31456, 31811, 32165, 32549, 32904, 33258, 33642, 33996, 34380, 34734,
    35089, 35473, 35827, 36182, 36566, 36920, 37304, 37658, 38012, 38396, 38751, 39105, 39489,
    39844, 40198, 40582, 40936, 41320, 41674, 42029, 42413, 42767, 43122, 43505, 43859, 44243,
    44597, 44952, 45336, 45691, 46045, 46429, 46783, 47137,
];

pub fn time_info(time: &str) -> String {
    let date = Date::from_str(time).unwrap();
    let week = date.week_in_year();
    let left_days = Date::year_days(date.year) - date.day_in_year();

    let unix_date = &date - &Date::new(1970, 1, 1);
    let index = LUNAR_SPRING_DELTA
        .binary_search(&(unix_date as u32))
        .unwrap_or_else(|x| x);
    let days_to_spring = LUNAR_SPRING_DELTA[index] - unix_date as u32 - 1;

    format!("{week},{left_days},{days_to_spring}")
}
